# Specifické příkazy pro prostředí Google Colab
if 'google.colab' in str(get_ipython()):
import os, sys
os.chdir('/content')
# Stažení knihovny
! ls parlamentikon || git clone "https://github.com/parlamentikon/parlamentikon.git" --branch main
os.chdir('/content/parlamentikon/notebooks')
instalace_zavislosti = True
if instalace_zavislosti:
! pip install -r ../requirements.txt 1>/dev/null
instalace_knihovny = False
if instalace_knihovny:
! pip install .. 1>/dev/null
else:
# Přidání cesty pro lokální import knihovny
import sys, os
sys.path.insert(0, os.path.abspath('..'))
from datetime import datetime, timedelta
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import numpy as np
from parlamentikon.Hlasovani import Hlasovani, ZpochybneniHlasovani, ZmatecneHlasovani
from parlamentikon.Schuze import Schuze
from parlamentikon.PoslanciOsoby import Poslanci
from nastav_notebook import nastav_pandas, clean_layout, clean_layout_with_x_spikes, clean_layout_with_y_spikes, clean_layout_with_xy_spikes, categorical_scale1
# nastavení výpisu, například zobrazení delších textů v sloupcích tabulek
nastav_pandas()
# formát výpisu data
format_den = "%d. %m. %Y"
# Data se budou pokaždé znovu stahovat z achivu PS
stahni=True
# Budeme analyzovat poslední volební období
zvolene_volebni_obdobi = None
# načti informace o schůzích PS (kdy začaly, v jakém jsou stavu, etc.)
sch = Schuze(stahni=stahni, volebni_obdobi=zvolene_volebni_obdobi)
sch.head(2)
2021-09-08:03:22:43 INFO [utility.py:21] Stahuji 'https://www.psp.cz/eknih/cdrom/opendata/poslanci.zip'. 2021-09-08:03:22:44 INFO [utility.py:21] Stahuji 'https://www.psp.cz/eknih/cdrom/opendata/schuze.zip'.
| id_schuze | id_org | schuze | od_schuze | do_schuze | aktualizace | pozvanka | text_dt | text_st | tm_line | stav | typ | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 469 | 172 | 1 | 2017-11-20 13:00:00+01:00 | NaT | 2017-11-08 08:58 | navržený pořad | \ | <NA> | <NA> | OK | 5 |
| 1 | 469 | 172 | 1 | 2017-11-20 13:00:00+01:00 | 2017-11-24 14:09:00+01:00 | 2017-11-24 14:09 | <NA> | \ | <NA> | <NA> | OK | 5 |
# načti informace o jednotlivých hlasováních
h = Hlasovani(stahni=stahni, volebni_obdobi=zvolene_volebni_obdobi)
h.head(2)
2021-09-08:03:22:47 INFO [utility.py:21] Stahuji 'https://www.psp.cz/eknih/cdrom/opendata/poslanci.zip'. 2021-09-08:03:22:48 INFO [utility.py:21] Stahuji 'https://www.psp.cz/eknih/cdrom/opendata/hl-2017ps.zip'.
| id_hlasovani | id_organ | schuze | cislo | bod | cas | pro | proti | zdrzel | nehlasoval | ... | kvorum | nazev_dlouhy | nazev_kratky | datum | bod__KAT | vysledek | druh_hlasovani | ma_zpochybneni | je_zmatecne | ma_stenozaznam | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 67018 | 172 | 1 | 1 | 3 | 13:53:00 | 191 | 0 | 5 | 0 | ... | 99 | Inf. o ustavení volební komise PS a volbě členů | <NA> | 2017-11-20 13:53:00+01:00 | normální | přijato | normální | False | True | False |
| 1 | 67019 | 172 | 1 | 2 | 3 | 13:53:00 | 194 | 0 | 4 | 0 | ... | 100 | Inf. o ustavení volební komise PS a volbě členů | <NA> | 2017-11-20 13:53:00+01:00 | normální | přijato | normální | False | False | False |
2 rows × 21 columns
zph = ZpochybneniHlasovani(stahni=stahni)
zph.head()
2021-09-08:03:22:52 INFO [utility.py:21] Stahuji 'https://www.psp.cz/eknih/cdrom/opendata/poslanci.zip'. 2021-09-08:03:22:53 INFO [utility.py:21] Stahuji 'https://www.psp.cz/eknih/cdrom/opendata/hl-2017ps.zip'.
| id_hlasovani | turn | mode | id_h2 | id_h3 | mode__KAT | je_platne | id_organ | schuze | cislo | ... | vysledek__ORIG | nazev_dlouhy | nazev_kratky | datum | bod__KAT | vysledek | druh_hlasovani | ma_zpochybneni | je_zmatecne | ma_stenozaznam | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 55680 | 67 | 0 | 55681 | 55682 | žádost o opakování | True | <NA> | <NA> | <NA> | ... | <NA> | <NA> | <NA> | NaT | <NA> | <NA> | <NA> | NaN | NaN | NaN |
| 1 | 55664 | 43 | 0 | 55665 | 55666 | žádost o opakování | True | <NA> | <NA> | <NA> | ... | <NA> | <NA> | <NA> | NaT | <NA> | <NA> | <NA> | NaN | NaN | NaN |
| 2 | 55590 | 244 | 0 | 55591 | 55592 | žádost o opakování | True | <NA> | <NA> | <NA> | ... | <NA> | <NA> | <NA> | NaT | <NA> | <NA> | <NA> | NaN | NaN | NaN |
| 3 | 55561 | 200 | 0 | 55562 | 55563 | žádost o opakování | True | <NA> | <NA> | <NA> | ... | <NA> | <NA> | <NA> | NaT | <NA> | <NA> | <NA> | NaN | NaN | NaN |
| 4 | 55555 | 199 | 0 | 55556 | 55557 | žádost o opakování | True | <NA> | <NA> | <NA> | ... | <NA> | <NA> | <NA> | NaT | <NA> | <NA> | <NA> | NaN | NaN | NaN |
5 rows × 30 columns
zmh = ZmatecneHlasovani(stahni=stahni, volebni_obdobi=zvolene_volebni_obdobi)
zmh.head()
2021-09-08:03:22:59 INFO [utility.py:21] Stahuji 'https://www.psp.cz/eknih/cdrom/opendata/poslanci.zip'. 2021-09-08:03:23:00 INFO [utility.py:21] Stahuji 'https://www.psp.cz/eknih/cdrom/opendata/hl-2017ps.zip'.
| id_hlasovani | id_organ | schuze | cislo | bod | datum__ORIG | cas | pro | proti | zdrzel | ... | vysledek__ORIG | nazev_dlouhy | nazev_kratky | datum | bod__KAT | vysledek | druh_hlasovani | ma_zpochybneni | je_zmatecne | ma_stenozaznam | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 6018 | <NA> | <NA> | <NA> | <NA> | <NA> | NaN | <NA> | <NA> | <NA> | ... | <NA> | <NA> | <NA> | NaT | <NA> | <NA> | <NA> | NaN | NaN | NaN |
| 1 | 7496 | <NA> | <NA> | <NA> | <NA> | <NA> | NaN | <NA> | <NA> | <NA> | ... | <NA> | <NA> | <NA> | NaT | <NA> | <NA> | <NA> | NaN | NaN | NaN |
| 2 | 7506 | <NA> | <NA> | <NA> | <NA> | <NA> | NaN | <NA> | <NA> | <NA> | ... | <NA> | <NA> | <NA> | NaT | <NA> | <NA> | <NA> | NaN | NaN | NaN |
| 3 | 7854 | <NA> | <NA> | <NA> | <NA> | <NA> | NaN | <NA> | <NA> | <NA> | ... | <NA> | <NA> | <NA> | NaT | <NA> | <NA> | <NA> | NaN | NaN | NaN |
| 4 | 7934 | <NA> | <NA> | <NA> | <NA> | <NA> | NaN | <NA> | <NA> | <NA> | ... | <NA> | <NA> | <NA> | NaT | <NA> | <NA> | <NA> | NaN | NaN | NaN |
5 rows × 24 columns
p = Poslanci(stahni=stahni, volebni_obdobi=zvolene_volebni_obdobi)
2021-09-08:03:23:03 INFO [utility.py:21] Stahuji 'https://www.psp.cz/eknih/cdrom/opendata/poslanci.zip'. 2021-09-08:03:23:04 WARNING [Snemovna.py:149] While merging 'funkce' with 'typ_funkce': Dropping ['id_typ_organ__typ_funkce', 'nazev_typ_organ_en__typ_funkce', 'typ_id_typ_organ__typ_funkce', 'typ_organ_obecny__typ_funkce', 'nazev_typ_organ_cz__typ_funkce'] because of abundance.
volebni_obdobi = h.volebni_obdobi
snemovna = h.snemovna
print(f"Poslanecká sněmovna bude analyzovaná pro volební období {volebni_obdobi}.")
Poslanecká sněmovna bude analyzovaná pro volební období 2017.
pocet_poslancu_dle_klubu = p[p.do_parlament.isna()].groupby('zkratka_klub').size().sort_values(ascending=False)
fig = go.Figure(go.Bar(
x=pocet_poslancu_dle_klubu.index,
y=pocet_poslancu_dle_klubu.values,
marker=dict(
color=list(range(len(pocet_poslancu_dle_klubu.index))),
colorscale=categorical_scale1
),
hovertemplate="<b>%{x}</b><br>Počet poslanců: %{y}<extra></extra>"
))
layout = go.Layout(
title="Aktuální počet poslanců dle poslaneckého klubu (strany)",
xaxis=dict(title="Strana", type='category'),
yaxis=dict(title="Počet poslanců")
)
fig.update_layout(clean_layout_with_y_spikes)
fig.update_layout(layout)
fig.show()
fig = go.Figure()
schuze = sch[sch.pozvanka.isna()]
for _, s in schuze.iterrows():
id_schuze = s.schuze
dny_hlasovani = h[h.schuze == id_schuze].datum.dt.date.unique()
od = s.od_schuze
do = sch.tzn.localize(datetime.today()) if pd.isna(s.do_schuze) else s.do_schuze
dny = [od.date()] + [d for d in dny_hlasovani if (d >= od.date()) and ((d <= do.date()) | pd.isna(do))] + [do.date()]
dny = list(set(dny))
datum_hovertemplate = f"od {od.strftime(format_den)}" if pd.isna(s.do_schuze) \
else f"{od.strftime(format_den)} - {s.do_schuze.strftime(format_den)}"
tm_line_hovertemplate= '' if pd.isna(s.tm_line) else s.tm_line
fig.add_trace(go.Scatter(
x=dny,
y=[s.schuze]*len(dny),
text=s.schuze,
hovertemplate=f"Schůze {s.schuze}<br>" \
"Datum: %{x}<br>"\
f"Trvání schůze: {datum_hovertemplate}<br>"\
f"Typ schůze: {s.typ}<br>" \
f"{tm_line_hovertemplate}<extra></extra>",
mode="lines+markers",
line = dict(shape='linear', width=15),
marker = dict(symbol='star-diamond', size=10),
))
fig.update_traces(marker=dict(line=dict(width=1, color='black')))
fig.update_layout(clean_layout_with_xy_spikes)
layout = go.Layout(
title="Schůze poslanecké sněmovny v čase",
xaxis=dict(title="Čas"),
yaxis=dict(title="Schůze", type='category'),
height=600,
showlegend=False
)
fig.update_layout(layout)
fig.show()
import plotly.express as px
h["pocet_dni_na_schuzi"] = h.groupby(["schuze"]).datum.transform('nunique')
h['den'] = h.datum.dt.date
h["den_schuze"] = h.groupby(["schuze", h.datum.dt.date]).ngroup()
h["den_schuze_min"] = h.groupby(["schuze"]).den_schuze.transform(min)
h["den_schuze_rank"] = (h["den_schuze"] - h["den_schuze_min"] + 1)
x1 = h.groupby(["schuze", 'den', "den_schuze_rank"]).size()
z1 = x1.reset_index(name="pocet_hlasovani")
print(f"Na schůzích {sorted(set(range(z1.schuze.max() + 1)) - set(z1.schuze.unique()) - set([0]))} se nehlasovalo.")
fig = px.bar(z1, x="schuze", y="pocet_hlasovani",
color="den_schuze_rank",
hover_data=['schuze', 'den_schuze_rank', 'pocet_hlasovani', 'den'],
labels={'schuze':'Schůze', 'den': 'Datum', 'den_schuze_rank': 'Pořadí dne schůze', 'pocet_hlasovani': 'Počet hlasování'},
title="Počet hlasování dle dne schůze")
layout = go.Layout(
title="Počet hlasování dle schůze",
plot_bgcolor="#FFFFFF",
#hovermode="x",
#hoverdistance=100, # Distance to show hover label of data point
#spikedistance=1000, # Distance to show spike
xaxis=dict(
title="Schůze sněmovny",
linecolor="#BCCCDC",
#type='category'
),
yaxis=dict(
title="Počet hlasování",
linecolor="#BCCCDC",
showspikes=True,
spikethickness=1,
spikedash="dot",
spikecolor="#999999",
spikemode="across",
)
)
fig.update_layout(layout)
Na schůzích [10, 21, 71] se nehlasovalo.
def pocet_hlasovani_dle_data(df, resample_to, resample_str, resample_label):
frame = df.set_index('datum').resample(resample_to).size()
frame = frame.mask(frame == 0, None).dropna()
max_idx = frame.sort_values().index[-1]
min_idx = frame.sort_values().index[0]
print(f"Nejvíce hlasování ({frame.loc[max_idx]}) se uskutečnilo {max_idx.strftime(resample_str)}.")
print(f"Nejméně hlasování ({frame.loc[min_idx]}) se uskutečnilo {min_idx.strftime(resample_str)}.")
fig = go.Figure()
fig.add_trace(go.Bar(
x=frame.index,
y=frame.values,
marker=dict(
color=frame.values,
colorscale='Bluered'
),
hovertemplate="%{x}<br>počet hlasování: %{y}<extra></extra>"
))
fig.update_layout(
title=f"Počet hlasování dle data ({resample_label})",
xaxis_title=f"datum ({resample_label})",
yaxis_title="počet hlasování",
width=1200,
height=500
)
dt_all = pd.date_range(start=frame.index[0],end=frame.index[-1])
dt_obs = [d.strftime(resample_str) for d in frame.index]
dt_breaks = [d for d in dt_all.strftime(resample_str).tolist() if not d in dt_obs]
# nezobrazuj data bez hlasování
#dt_all = pd.date_range(start=df.index[0], end=df.index[-1])
#dt_obs = [d.strftime(resample_str) for d in df.index]
#dt_breaks = [d for d in dt_all.strftime(resample_str).tolist() if not d in dt_obs]
fig.update_xaxes(
rangebreaks=[dict(values=dt_breaks)]
)
fig.show()
pocet_hlasovani_dle_data(h, "D", "%Y-%m-%d", "den")
Nejvíce hlasování (387) se uskutečnilo 2019-06-05. Nejméně hlasování (1) se uskutečnilo 2021-06-21.
Následující analýza slouží k tomu, abychom lépe pochopili, jakým způsobem se hlasujev PS ČR. Proč je tomu třeba rozumět? Některá hlasování (například hlasování o zpochybnění hlasování) mají technický charakter. V některých analýzách je může být vhodné vynechat.
Přibližná pravidla pro určení platnosti hlasování:
hl_pocet = h.id_hlasovani.nunique()
zpochybneni = zph[zph.je_platne == True]
zm_hl = h[h.id_hlasovani.isin(zmh.id_hlasovani)]
zm_hl_pocet = zm_hl.id_hlasovani.nunique()
zp_hl = h[h.id_hlasovani.isin(zpochybneni.id_hlasovani)]
zp_hl_pocet = zp_hl.id_hlasovani.nunique()
pouze_v_tabulce_zpochybneni_pocet = len(set(zph.id_hlasovani) - set(h.id_hlasovani))
pouze_v_tabulce_zmatecne_pocet = len(set(zmh.id_hlasovani) - set(h.id_hlasovani))
print(f"Základní vlastnosti tabulky Hlasovani (pro sněmovnu {h.volebni_obdobi}):")
print(f"- {hl_pocet} hlasování, z toho")
print(f"- {zm_hl_pocet} ({100 * (zm_hl_pocet / hl_pocet):.2f}%) hlasování bylo označeno za zmatečné,")
print(f"- {zp_hl_pocet} ({100 * (zp_hl_pocet / hl_pocet):.2f}%) hlasování bylo zpochybněno.")
df = zpochybneni[zpochybneni.mode__KAT == 'pouze pro stenozáznam']
v1 = h[h.id_hlasovani.isin(df.id_hlasovani)].id_hlasovani.nunique()
print(f"\t- {v1} hlasování z tabulky Hlasování bylo zpochybněno, ale zpochybnění bylo uvedeno jen pro stenozáznam.")
df = zpochybneni[zpochybneni.mode__KAT == 'žádost o opakování']
v1 = h[h.id_hlasovani.isin(df.id_hlasovani)].id_hlasovani.nunique()
print(f"\t- {v1} hlasování z tabulky Hlasování bylo zpochybněno s žádostí o opakování hlasování.")
df = zpochybneni[(zpochybneni.mode__KAT == 'žádost o opakování') & ~(zpochybneni.id_h2.isna()| zpochybneni.id_h3.isna())]
v1 = h[h.id_hlasovani.isin(df.id_hlasovani)].id_hlasovani.nunique()
print(f"\t\t- {v1} hlasování z tabulky Hlasování bylo zpochybněno s žádostí o opakování hlasování, o zpochybnění se hlasovalo a původní hlasování se následně opakovalo.")
df = zpochybneni[(zpochybneni.mode__KAT == 'žádost o opakování') & ~zpochybneni.id_h2.isna() & zpochybneni.id_h3.isna()]
v1 = h[h.id_hlasovani.isin(df.id_hlasovani)].id_hlasovani.nunique()
print(f"\t\t- {v1} hlasování z tabulky Hlasování bylo zpochybněno s žádostí o opakování hlasování, o zpochybnění se hlasovalo, ale původní hlasování se neopakovalo.")
print()
print(f"Dalších {pouze_v_tabulce_zpochybneni_pocet} hlasování je uvedeno pouze v tabulce ZpochybneniHlasovani. ")
print(f"Dalších {pouze_v_tabulce_zmatecne_pocet} hlasování je uvedeno pouze v tabulce ZmatecneHlasovani.")
print()
print(f"Celkově tedy proběhlo ve sněmovně až {hl_pocet + pouze_v_tabulce_zpochybneni_pocet + pouze_v_tabulce_zmatecne_pocet} hlasování.")
print()
print("Poznámka: Tabulka ZmatecneHlasovani nemá explicitně určenou vazbu na danou sněmovnu. Její propojení s tabulkou Hlasování bylo provedeno následující heuristikou: "
"Pro danou sněmovnu se určí minimální a maximální identifikátor hlasování. Pro dané zmatečné hlasování pak musí platit, že min(id(hlasování)) <= id(zmatečného hlasování) <= max(id(hlasování)).")
Základní vlastnosti tabulky Hlasovani (pro sněmovnu 2017): - 10092 hlasování, z toho - 121 (1.20%) hlasování bylo označeno za zmatečné, - 248 (2.46%) hlasování bylo zpochybněno. - 155 hlasování z tabulky Hlasování bylo zpochybněno, ale zpochybnění bylo uvedeno jen pro stenozáznam. - 93 hlasování z tabulky Hlasování bylo zpochybněno s žádostí o opakování hlasování. - 92 hlasování z tabulky Hlasování bylo zpochybněno s žádostí o opakování hlasování, o zpochybnění se hlasovalo a původní hlasování se následně opakovalo. - 1 hlasování z tabulky Hlasování bylo zpochybněno s žádostí o opakování hlasování, o zpochybnění se hlasovalo, ale původní hlasování se neopakovalo. Dalších 512 hlasování je uvedeno pouze v tabulce ZpochybneniHlasovani. Dalších 684 hlasování je uvedeno pouze v tabulce ZmatecneHlasovani. Celkově tedy proběhlo ve sněmovně až 11288 hlasování. Poznámka: Tabulka ZmatecneHlasovani nemá explicitně určenou vazbu na danou sněmovnu. Její propojení s tabulkou Hlasování bylo provedeno následující heuristikou: Pro danou sněmovnu se určí minimální a maximální identifikátor hlasování. Pro dané zmatečné hlasování pak musí platit, že min(id(hlasování)) <= id(zmatečného hlasování) <= max(id(hlasování)).
def flatten(ary):
return [x for l in ary for x in l]
def fce_mezi_hlasovanim_a_hlasovanim_o_zpochybneni_hlasovani_ids(row):
if pd.isna(row['id_h2']):
return []
else:
return list(range(row['id_hlasovani']+1, row['id_h2']))
# Hlasování o zpochybnění hlasování je možné také zkazit nebo zpochybnit.
# Mezi prvním hlasováním o zpochybnění a opakovaným hlasováním může proběhnout několik dalších zpochybněných nebo neplatných hlasování.
def fce_mezi_hlasovanim_o_zpochybneni_a_opakovanym_hlasovanim_ids(row):
if pd.isna(row['id_h2']):
return []
elif pd.isna(row['id_h3']):
return []
else:
return list(range(row['id_h2']+1, row['id_h3']))
mezi_hlasovanim_a_hlasovanim_o_zpochybneni_hlasovani_ids = \
flatten(zpochybneni[zpochybneni.mode__KAT == 'žádost o opakování'].apply(fce_mezi_hlasovanim_a_hlasovanim_o_zpochybneni_hlasovani_ids, axis=1))
mezi_hlasovanim_o_zpochybneni_a_opakovanym_hlasovani_ids = \
flatten(zpochybneni[zpochybneni.mode__KAT == 'žádost o opakování'].apply(fce_mezi_hlasovanim_o_zpochybneni_a_opakovanym_hlasovanim_ids, axis=1))
hlasovani_o_zpochybneni_ids = h[h.id_hlasovani.isin(zpochybneni.id_h2.unique())]
hlasovani_bez_zmatecnych_a_zpochybnenych = h[~h.id_hlasovani.isin(zmh.id_hlasovani)
& ~h.id_hlasovani.isin(zph[zph.mode__KAT == 'žádost o opakování'].id_hlasovani)
& ~h.id_hlasovani.isin(mezi_hlasovanim_o_zpochybneni_a_opakovanym_hlasovani_ids)
& ~h.id_hlasovani.isin(hlasovani_o_zpochybneni_ids)
]
platne_cnt = len(hlasovani_bez_zmatecnych_a_zpochybnenych)
print(f"Za platné lze považovat {platne_cnt} ({100*platne_cnt/len(h):.2f}%) hlasování z {len(h)}.")
Za platné lze považovat 9871 (97.81%) hlasování z 10092.
print(f"Poslední běh notebooku: {datetime.now().strftime('%d.%m.%Y %H:%M:%S')}.")
Poslední běh notebooku: 08.09.2021 03:23:07.